关于Go语言、VS-code的一些Tips

undefined1、struct的序列化

struct中的变量命名必须以大写字母开头,否则在使用以下方法序列化时候,其值不会被序列化到[]byte之中,也就是说,反序列化后,其值会丢失。

  1. func (b *Block) Serialize() []byte {
  2. varresult bytes.Buffer //定义一个buffer存储序列化后的数据
  3. //初始化一个encoder,gob是标准库的一部分
  4. //encoder根据参数的类型来创建,这里将编码为字节数组
  5. encoder := gob.NewEncoder(&result)
  6. err := encoder.Encode(b) //编码
  7. if err != nil {
  8. log.Panic(err) //如果出错,将记录log后,Panic调用,立即终止当前函数的执行
  9. }
  10. return result.Bytes()
  11. }

undefined2、VS-CODE的Debug配置

关于Go语言、VS-code的一些Tips - 图1

如果程序是通过命令行参数运行的,配置文件参考如下:

  1. {
  2. "version": "0.2.0",
  3. "configurations": [
  4. {
  5. "name": "Launch",
  6. "type": "go",
  7. "request": "launch",
  8. "mode": "auto",
  9. "remotePath": "",
  10. "port": 5546,
  11. "program": "${fileDirname}",
  12. "env": {},
  13. "args": ["createblockchain","-address","Ivan"]//main的执行命令:main createblockchain -address Ivan
  14. //"args": ["printchain"]//main的执行命令:main printchain
  15. }
  16. ]
  17. }

undefined3、flag包是解析命令行参数输入的很好的工具

(1)定义CLI处理器

  1. typeCLIstruct{}

(2)检测命令的合法性(普通检查)

  1. //validateArgs 校验命令,如果无效,打印使用说明
  2. //参数是否符合命令要求,不在这里进行检查,而在执行命令前检查
  3. func (cli \*CLI) validateArgs() {
  4. iflen(os.Args) < 2 { //所有命令至少有两个参数,第一个是程序名称,第二个是命名名称
  5. cli.printUsage()
  6. os.Exit(1)
  7. }
  8. }

(3)命令行使用说明

  1. //printUsage 打印命令行帮助信息
  2. func (cli \*CLI) printUsage() {
  3. fmt.Println("Usage:")
  4. fmt.Println(" getbalance -address ADDRESS - 获得某个地址的余额")
  5. fmt.Println(" createblockchain -address ADDRESS - 创建一个新的区块链并发送创始区块奖励给到address")
  6. fmt.Println(" printchain - 打印区块链中的所有区块")
  7. fmt.Println(" send -from FROM -to To -amount - 发送amount数量的币,从地址FROM到TO")
  8. }

(4)Run 读取命令行参数,执行相应的命令

  1. //使用标准库里面的 flag 包来解析命令行参数:
  2. func (cli *CLI) Run() {
  3. cli.validateArgs()
  4. //定义名称为"getbalance"的空的flagset集合
  5. getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
  6. //定义名称为"createBlockchainCmd"的空的flagset集合
  7. createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
  8. //定义名称为"sendCmd"的空的flagset集合
  9. sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
  10. //定义名称为"printchain"的空的flagset集合
  11. printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
  12. //String用指定的名称给getBalanceAddress 新增一个字符串flag
  13. //以指针的形式返回getBalanceAddress
  14. getBalanceAddress := getBalanceCmd.String("address", "", "获得金钱的地址")
  15. createBlockchainAddress := createBlockchainCmd.String("address", "", "接受挖出创始区块奖励的的地址")
  16. sendFrom := sendCmd.String("from", "", "钱包源地址")
  17. sendTo := sendCmd.String("to", "", "钱包目的地址")
  18. sendAmount := sendCmd.Int("amount", 0, "转移资金的数量")
  19. //os.Args包含以程序名称开始的命令行参数
  20. switch os.Args[1] { os.Args[0]为程序名称,真正传递的参数index1开始,一般而言Args\[1\]为命令名称
  21. case"getbalance":
  22. //Parse调用之前,必须保证getBalanceCmd所有的flag都已经定义在其中
  23. err := getBalanceCmd.Parse(os.Args\[2:\]) //仅解析参数,不含命令
  24. if err != nil {
  25. log.Panic(err)
  26. }
  27. case"createblockchain":
  28. err := createBlockchainCmd.Parse(os.Args\[2:\])
  29. if err != nil {
  30. log.Panic(err)
  31. }
  32. case"printchain":
  33. //Parse调用之前,必须保证addBlockCmd所有的flag都已经定义在其中
  34. //根据命令设计,这里将返回nil,所以在前面没有定义接收解析后数据的flag
  35. //但printChainCmd的parsed=true
  36. err := printChainCmd.Parse(os.Args\[2:\]) //仅仅解析参数,不含命令
  37. if err != nil {
  38. log.Panic(err)
  39. }
  40. case"send":
  41. err := sendCmd.Parse(os.Args\[2:\])
  42. if err != nil {
  43. log.Panic(err)
  44. }
  45. default:
  46. cli.printUsage()
  47. os.Exit(1)
  48. }
  49. if getBalanceCmd.Parsed() {
  50. if *getBalanceAddress == "" {
  51. getBalanceCmd.Usage()
  52. os.Exit(1)
  53. }
  54. cli.getBalance(\*getBalanceAddress)
  55. }
  56. if createBlockchainCmd.Parsed() {
  57. if *createBlockchainAddress == "" {
  58. createBlockchainCmd.Usage()
  59. os.Exit(1)
  60. }
  61. cli.createBlockchain(\*createBlockchainAddress)
  62. }
  63. if printChainCmd.Parsed() {
  64. cli.printChain()
  65. }
  66. if sendCmd.Parsed() {
  67. if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
  68. sendCmd.Usage()
  69. os.Exit(1)
  70. }
  71. cli.send(\*sendFrom, \*sendTo, \*sendAmount)
  72. }
  73. }

undefined4、github与vs-code集成,同时打开多个github控制的项目

有时候,我们需要在一个vs-code界面上,同时打开多个受github控制的项目(每个项目为独立的github仓库),其做法如下:

(1)在本地磁盘建立一个顶级文件夹;

(2)打开VS-CODE,克隆存储库

关于Go语言、VS-code的一些Tips - 图2

刚刚建立的本地顶级文件夹,输入项目github地址。

(3)VS-CODE关闭文件夹,然后打开刚建立的顶级文件夹

这时候,所有的子文件夹会自动导入进来了。

顶级文件夹下的各子文件夹对应于子项目,独立进行github更新。

平时工作,除非建立分支,一般操作三板斧:Add(+)、Commit、Push,将本地修改同步到云端。

undefined5、map类型的使用

map是go里面非常强大的类型,对于值为struct类型、数组或其他非常复杂的结构的key-value列表,非常适合使用它来进行存储处理。

undefined6、append的两种用法

  1. x := []int {1,2,3}
  2. y := []int {4,5,6}
  3. //注意下面这两个区别
  4. fmt.Println(append(x,4,5,6))
  5. fmt.Println(append(x,y...));

第一种用法中,第一个参数为slice,后面可以添加多个参数,这种情况一般是将一个或多个元素加入到现有的数组之中:

x=append(x,4,5,6)

第二种用法,是将两个slice拼接在一起,在第二个slice的名称后面加三个点,构建一个新的数组,这时候append只支持两个参数,不支持任意个数的参数。

z:=append(x,y…)

undefined7、数组的切片引用方式:复用

我们一般在构建了数组实例后,通过函数或方法返回给调用者使用,一般不会直接返回数组指针,而是以切片形式,返回给调用者。这种方式是Go里面的普遍方式。函数或方法内部也是这么使用。

  1. func checksum(payload []byte) []byte {
  2. firstSHA := sha256.Sum256(payload)
  3. secondSHA := sha256.Sum256(firstSHA[:])//引用firstSHA 数组的全部切片
  4. return secondSHA[:addressChecksumLen]//返回用secondSHA的部分切片
  5. }

undefined8、判断文件是否存在

  1. if_, err := os.Stat(walletFile); os.IsNotExist(err) {
  2. return err
  3. }

undefined9、[]byte转为字符串:

hex.EncodeToString(prevTX.ID)//prevTX.ID为[]byte类型

undefined10、break语句

(1)break//直接退出当前层次的循环

(2)break WORK//退出WORK标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上

undefined11、vs-code多命令窗口运行

vs-code可以多窗口运行。

关于Go语言、VS-code的一些Tips - 图3

每一个窗口都可以独立设置环境变量

如第一个窗口设置:set NODE_ID=3000,第二个窗口设置环境变量:set NODE_ID=3001,第三个窗口设置为set NODE_ID=3002。

在命令行窗口运行服务后,如希望暂停,通过键盘上ctrl+c组合键执行。

undefined12、go env

可以使用命令行查看 Go 开发包的环境变量配置信息,这些配置信息里可以查看到当前的 GOPATH 路径设置情况。在命令行中运行go env

undefined13、gobEncode

go内置的gobEncode,可以将一个stuct转换成一个[]byte数组,一个典型的用途是传输远程过程调用(RPC)的参数和结果:

  1. data := block{nodeAddress, b.Serialize()}
  2. payload := gobEncode(data)
  1. func gobEncode(data interface{}) []byte {
  2. varbuff bytes.Buffer
  3. enc := gob.NewEncoder(&buff)
  4. err := enc.Encode(data)
  5. if err != nil {
  6. log.Panic(err)
  7. }
  8. return buff.Bytes()
  9. }

与此对应的是## GobDecoder。

undefined14、通道chan

chan 可以理解为队列,遵循先进先出的规则,用于在不同goroutine之间通信。

  1. // 声明不带缓冲的通道
  2. ch1 := make(chan string)
  3. // 声明带10个缓冲的通道
  4. ch2 := make(chan string, 10)
  5. // 声明只读通道
  6. ch3 := make(<-chan string)
  7. // 声明只写通道
  8. ch4 := make(chan<- string)

注意:

不带缓冲的通道,进和出都会阻塞。

带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。

undefined写入 chan

  1. ch1 := make(chan string, 10)
  2. ch1 <- "a"

undefined读取 chan

  1. val, ok := <- ch1 // 或
  2. val := <- ch1

undefined关闭 chan

  1. close(chan)

注意:

  • close 以后不能再写入,写入会出现 panic
  • 重复 close 会出现 panic
  • 只读的 chan 不能 close
  • close 以后还可以读取数据